<?php


/**
 * Description of AdminPresenter
 *
 * @author David Dusek
 */

use Nette\Application\UI\Form,
    CMS\Module\Exceptions;

class UpdatePresenter extends AdminPresenter
{   
    /**
     * Sekce update ze session.
     * @var object 
     */
    protected $sSection;   
    
    protected function startup() {
        parent::startup();
                
        $this->sSection = $this->getContext()->session->getSection('update');
    }
    
    public function renderDefault() {        
        $this->template->title = $this->getTitle("title", "update");
        $this->isAllowed("update");    
        
        $json = $this->getUpdateConfig();
        if ($json !== FALSE) {
            $conf = json_decode($json, true);
            
            if ($conf === NULL) {
                $this->flashMessage($this->translate("configFileCorrupted"), "error");
                return;
            }
            
            $this->sSection->config = $conf;
            $this->template->config = $conf;
            $this->template->appDir = APP_DIR;            
        } else {
            $this->flashMessage($this->translate("configFileNotFound"), "error");
            return;
        }
    }
    
    public function renderSettings() {
        $this->template->title = $this->getTitle("title", "settings");
        $this->isAllowed("settings");
    }
    
    /**
     * Nacteni aktualni konfigurace z konfiguracniho souboru.
     * @return string|FALSE Pokud sobor existuje a lze ho cist tak je vracen jeho
     * obsah jako retezec jinak FALSE. 
     */
    private function getUpdateConfig() {
        return @file_get_contents(MODULE_DIR . '/update/libs/config.json');
    }
    
    /**
     * Formular pro zmenu nastaveni aktualizace. 
     */
    protected function createComponentSettingsForm() {
        $form = new Form();
        $modules = $this->core->runHook("hookModuleGetModules", null, "ModuleModule");
        
        $moduleItems = array();
        if (is_array($modules)) {
            foreach ($modules as $module) {               
                $moduleItems[$module->name . "Module"] = $module->name;
            }
        }
        
        $form->addText("svnpath", $this->translate("svnPath"))
                ->setRequired($this->translate("requiredErrMsg"));
        $form->addText("svndir", $this->translate("svnDir"))
                ->setRequired($this->translate("requiredErrMsg"));
        $form->addText("svnname", $this->translate("svnName"));
        $form->addPassword("svnpass", $this->translate("svnPass"));
        $form->addText("bckdir", $this->translate("bckDir"))
                ->setRequired($this->translate("requiredErrMsg"));
        $form->addMultiSelect("modules", $this->translate("modules"), $moduleItems, 10);
        $form->addText("key", $this->translate("sharedKey"));
        
        $form->addSubmit("save", $this->translate("save"));
        $form->onSuccess[] = callback($this, 'settingsFormSubmitted');
        
        $content = $this->getUpdateConfig();
        if ($content !== FALSE) {
            $config = json_decode($content, TRUE);            
            $form->setDefaults($config);
        }
        
        return $form;
    }
    
    /**
     * Zpracovani formulare pro nastaveni aktualizace.
     * @param Form $form 
     */
    public function settingsFormSubmitted(Form $form) {
        $values = $form->getValues();
        $bckupDir = APP_DIR . "/" . $values["bckdir"];
        if (!is_dir($bckupDir) && !mkdir($bckupDir)) {
            $form->addError(vsprintf($this->translate("unableCreateBckupdir"), $bckupDir));
        } else {
            file_put_contents($bckupDir . "/../netterobots.txt", "Disallow: /" . $values["bckdir"]);
        }
                
        try {
            $svnClient = new phpsvnclient($values["svnpath"], $values["svnname"], $values["svnpass"]);           
        } catch (SvnRequestException $e) {
            $msg = "";
            if (strlen($e->getMessage()) > 0) {
                $msg = " (" . $e->getMessage() . ")";
            }
            $form->addError($this->translate("svnPathError") . $msg );
        }
        
        if (!$form->hasErrors()) {
            $values["bckdir"] = $json = json_encode($values);
            @file_put_contents(MODULE_DIR . '/update/libs/config.json', $json);
            $this->flashMessage($this->translate("settingsSaveMsg"));
            $this->redirect("this");
        }
    }
    
    /**
     * Komponenta gridito pro vypis predchozich verzi systemu a moznosti navratu
     * ke zvolene verzi.
     * @return \Gridito\Grid
     */
    protected function createComponentGrid() {
        $grid = new Gridito\Grid($this, 'grid');
       
        $qb = $this->em->getRepository('\CMS\Update\Models\Update')->createQueryBuilder('u')->orderBy("u.date", "desc");        
         
        $grid->setModel(new CMS\DoctrineQueryBuilderModel($qb));
//        $grid->addColumn('id', 'Id')->setSortable(true);
        $grid->addColumn('date', $this->translate('date'))->setSortable(true);
        $grid->addColumn('modules', $this->translate('modules'));
        $grid->addColumn('svnPath', $this->translate('svnPath'));
        $grid->addColumn('svnDir', $this->translate('svnDir'));
        $typeLocal = $this->translate("updateTypeLocal");
        $typeRemote = $this->translate("updateTypeRemote");
        $grid->addColumn('updateType', $this->translate('updateType'), array(
            "renderer" => function ($update) use ($typeLocal, $typeRemote) {
                echo $update->remote ? $typeRemote : $typeLocal;
            }
        ));
               
        if ($this->isAllowed("update")) {
            $grid->addButton('revert', $this->translate('revert'), array(
                            'icon' => 'ui-icon-seek-prev',
                            'link' => function ($update) use ($grid) {
                                return $grid->presenter->link("revert!", array("id" => $update->id));
                            },
                    ))
                    ->setConfirmationQuestion($this->translate('backupQuestion'))
                    ->setShowText(false)
                    ->setAjax(true);                        
        }           
    }
    
    /**
     * Inicializace aktualizace. Vytvoreni slozky pro zalohu.
     */
    public function handleUpdateInit() {
        $this->isAllowed("update");
        $this->invalidateControl();       

        if (empty($this->sSection->config)) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("configNotSet");
            return;
        }
            
        $bckdir = $this->makeBackupDir($this->sSection->config);
        if ($bckdir === FALSE) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("createBckupDirFailed");                                
            return;
        }
        $this->sSection->bckdir = $bckdir;        
        
        $this->payload->err = FALSE;
    }         
    
    /**
     * Vytvoreni slozky pro zalohu.
     * @param array $config
     * @return boolean|string Pokud se vytvoreni slozky podari tak cestu ke 
     * slozce jinak FALSE.
     */
    public function makeBackupDir($config) {
        $this->isAllowed("update");
        
        $bckdir = APP_DIR . "/" . $config["bckdir"] . "/" . date("Y-m-d_H-i-s");
        if (!file_exists($bckdir) && !mkdir($bckdir)) {            
            return FALSE;
        }
        
        return $bckdir;
    }
    
    /**
     * Provede zalohu databaze.
     */
    public function handleBackupDatabase() {
        $this->isAllowed("update");
        $this->invalidateControl();
        
        if (empty($this->sSection->bckdir)) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("backupDirNotSet");
            return;
        }
        
        $databaseBckupDir = $this->sSection->bckdir . "/database_dump/";
        UpdateModule::backupDatabase($this->em, $databaseBckupDir);
        
        $this->payload->err = FALSE;
    }
    
    /**
     * Zaznamena informace o provedene aktualizaci do databaze.
     */
    public function handleLogToDb() {
        $this->isAllowed("update");
        $this->invalidateControl();
        
        if (empty($this->sSection->bckdir)) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("backupDirNotSet");
            return;
        }
        
        if (empty($this->sSection->config)) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("configNotSet");
            return;
        }
        
        $updateDB = new CMS\Update\Models\Update();
        $updateDB->setModules(implode(", ", $this->sSection->config["modules"]));
        $updateDB->setBackupDir(realpath($this->sSection->bckdir));
        $updateDB->setSvnPath($this->sSection->config["svnpath"]);
        $updateDB->setSvnDir($this->sSection->config["svndir"]);            
        $this->em->persist($updateDB);
        $this->em->flush();
        
        $this->payload->err = FALSE;
    }
    
    /**
     * Zaloha stavajicich souboru.
     */
    public function handleBackupCurrentVersion() {        
        $this->isAllowed("update");
        $this->invalidateControl();
        
        if (empty($this->sSection->bckdir)) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("backupDirNotSet");
            return;
        }
        
        if (empty($this->sSection->config)) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("configNotSet");
            return;
        }
        
        $destDir = $this->sSection->bckdir . "/modules";
        if (!file_exists($destDir)) {
            mkdir($destDir);
        }
        foreach ($this->sSection->config["modules"] as $module) {
            $moduleName = lcfirst(substr($module, 0, strripos($module, "Module")));
            UpdateModule::recurseCopy(MODULE_DIR . "/$moduleName", $destDir . "/$moduleName");
        }        
                        
        // zaloha jadra
        $coreBckupDir = $this->sSection->bckdir . "/core";
        if (!file_exists($coreBckupDir)) {
            mkdir($coreBckupDir);
        }
        UpdateModule::recurseCopy(LIBS_DIR . "/CMS", $coreBckupDir . "/libs");
        UpdateModule::recurseCopy(WWW_DIR . "/CMS", $coreBckupDir . "/www");
        
        $this->payload->err = FALSE;
    }
    
    /**
     * Ziskani nove verze systemu z SVN a jejich nahrani na prisluzne misto.
     */
    public function handleLoadNewFiles() {
        $this->isAllowed("update");
        $this->invalidateControl();
        
        if (empty($this->sSection->config)) {
            $this->payload->err = TRUE;
            $this->payload->errMsg = $this->translate("configNotSet");
            return;
        }
        
        // smazani puvodnich souboru
        foreach ($this->sSection->config["modules"] as $module) {
            $moduleName = lcfirst(substr($module, 0, strripos($module, "Module")));
            UpdateModule::recurseDelete(MODULE_DIR . "/$moduleName");
        }       

        // nahrani novych souboru
        $svnClient = new phpsvnclient($this->sSection->config["svnpath"], $this->sSection->config["svnname"], $this->sSection->config["svnpass"]);            
        foreach ($this->sSection->config["modules"] as $module) {
            $moduleName = lcfirst(substr($module, 0, strripos($module, "Module")));
            $svnClient->checkOut($this->sSection->config["svndir"] . "/app/modules/$moduleName", MODULE_DIR . "/$moduleName");
        }
        
        UpdateModule::recurseDelete(LIBS_DIR . "/CMS");
        $svnClient->checkOut($this->sSection->config["svndir"] . "/libs/CMS", LIBS_DIR . "/CMS");
        
        UpdateModule::recurseDelete(WWW_DIR . "/CMS");
        $svnClient->checkOut($this->sSection->config["svndir"] . "/www/CMS", WWW_DIR . "/CMS");
        unset($this->sSection->bckdir);        
    } 
    
    /**
     * Po uspesnem dokonceni aktualizace se prekresli vypis aktualizaci. 
     */
    public function handleUpdateFinish() {
         $this->invalidateControl("gridWrapper");
    }
    
    /**
     * Funkce pro vraceni zmen pri neuspesnem updatu. 
     */
    public function handleRollback() {   
        $this->invalidateControl();
        
        if (!empty($this->sSection->config) && !empty($this->sSection->bckdir)) {
            $upRep = $this->em->getRepository("\CMS\Update\Models\Update");
            $version = $upRep->findOneBy(array("backupDir" => $this->sSection->bckdir));
            
            // odstranime zaznam z databaze.
            if ($version !== null) {
                $this->em->remove($version);
                $this->em->flush();
            }
              
            // pokud jiz byly zazalohovane nejake moduly nebo jadro tak nahrajeme 
            // soubory zpet
            if (!empty($this->sSection->bckdir) && file_exists($this->sSection->bckdir)) {
                $bckupDir = $this->sSection->bckdir;

                $modulePath = $bckupDir . "/modules";
                if (file_exists($modulePath)) {
                    $dir = opendir($modulePath);
                    
                    while (false !== ($file = readdir($dir))) {
                        if (( $file != '.' ) && ( $file != '..' )) {
                            if (is_dir($modulePath . '/' . $file)) {
                                UpdateModule::recurseDelete(MODULE_DIR . "/$file");
                                UpdateModule::recurseMove($modulePath . "/$file", MODULE_DIR . "/$file");
                            }
                        }                    
                    }
                    closedir($dir);
                }
                
                if (file_exists($bckupDir . "/core/libs")) {
                    UpdateModule::recurseDelete(LIBS_DIR . "/CMS");
                    UpdateModule::recurseMove($bckupDir . "/core/libs", LIBS_DIR . "/CMS");
                }
                
                if (file_exists($bckupDir . "/core/www")) {
                    UpdateModule::recurseDelete(WWW_DIR . "/CMS");
                    UpdateModule::recurseMove($bckupDir . "/core/www", WWW_DIR . "/CMS");
                }
                
                // odstranime samotnou slozku se zalohou
                UpdateModule::recurseDelete($bckupDir);
                rmdir($bckupDir);
            }
        }
        
        unset($this->sSection->bckdir);
    }
    
    /**
     * Provede navrat na predchozi verzi systemu. Predchozi verze bude ziskana
     * z databaze podle id.
     * @param int $id Id verze na kterou se provadi navrat.
     */
    public function handleRevert($id) {
        $this->invalidateControl();
        
        $upRep = $this->em->getRepository("\CMS\Update\Models\Update");
        $version = $upRep->find($id);
        
        if ($version === null) {
            $this->flashMessage($this->translate("unknownVersion"), "error");
            return;
        }
        
        if (!$this->checkVersion($version)) {
            $this->flashMessage($this->translate("versionCorrupted"), "error");
            return;
        }
        
        // fyzicke odstraneni novejsich verzi
        $verForDel = $upRep->getNewer($version->date);
        foreach ($verForDel as $newerVersion) {
            UpdateModule::recurseDelete($newerVersion->backupDir);
            rmdir($newerVersion->backupDir);
        }
                
        // smazani aktualnich souboru
        UpdateModule::recurseDelete(LIBS_DIR . "/CMS");
        UpdateModule::recurseDelete(WWW_DIR . "/CMS");
        $modules = explode(',', $version->modules);
        foreach ($modules as $module) {
            $moduleName = lcfirst(substr($module, 0, strrpos($module, "Module")));
            UpdateModule::recurseDelete(MODULE_DIR . "/$moduleName");
        }
        
        // kopirovani novych souboru
        UpdateModule::recurseCopy($version->backupDir . "/core/libs", LIBS_DIR . "/CMS");
        UpdateModule::recurseCopy($version->backupDir . "/core/www", WWW_DIR . "/CMS");
        $modules = explode(',', $version->modules);
        foreach ($modules as $module) {
            $moduleName = lcfirst(substr($module, 0, strrpos($module, "Module")));
            UpdateModule::recurseCopy($version->backupDir . "/modules/$moduleName", MODULE_DIR . "/$moduleName");
        }
        
        // provedeni spkriptu pro navraceni DB
        $SQLFile = $version->backupDir . "/database_dump/dump.sql";                
        $createScript = file_get_contents($SQLFile);
        $this->em->getConnection()->exec($createScript);
        
        // smazani zalohy verze na kterou se vraci
        UpdateModule::recurseDelete($version->backupDir);
        rmdir($version->backupDir);
        
        $this->flashMessage($this->translate("revertSucc"));
    }
        
    /**
     * Kontrola jestli je dana zaloha kompletni.
     * @param \CMS\Update\Models\Update $version
     * @return boolean 
     */
    private function checkVersion($version) {
        $version->backupDir;
        
        // kontrola jestli existuje zaloha vsech aktulizovanych modulu
        $modDir = $version->backupDir . "/modules";
        $modules = explode(',', $version->modules);
        foreach ($modules as $module) {
            $moduleName = lcfirst(substr($module, 0, strrpos($module, "Module")));
            if (!file_exists($modDir . "/$moduleName")) {
                return FALSE;
            }
        }
        
        // kontrola existence zalohy jadra
        if (!file_exists($version->backupDir . "/core/libs") 
                || !file_exists($version->backupDir . "/core/www")) {
            return FALSE;
        }       
        
        // kontrola existence zalohy databaze
        if (!file_exists($version->backupDir . "/database_dump/dump.sql")) {
            return FALSE;
        }
        
        return TRUE;
    }
   
}
